package com.imooc.demo.common.util;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.DocumentException;

import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.*;
import java.util.Map.Entry;

/**
 * @className: SignatureUtils
 * @description: 签名和验证签名工具类
 * @date: 2020/8/21
 * @author: cakin
 */
public class SignatureUtils {
    /**
     * 签名,MD5.
     *
     * @param paramMap 参数Map,不包含商户秘钥且顺序确定
     * @param key      商户秘钥
     * @return 签名串
     */
    public static String sign(Map<String, Object> paramMap, String key) {
        if (key == null) {
            throw new RuntimeException("key不能为空");
        }
        String sign = createSign(paramMap, key);
        return sign;
    }

    /**
     * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     *
     * @param paramMap 参数Map,不包含商户秘钥且顺序确定
     * @param key      商户秘钥
     * @return 签名串
     */
    private static String createSign(Map<String, Object> paramMap, String key) {
        StringBuffer sb = new StringBuffer();
        SortedMap<String, Object> sort = new TreeMap<String, Object>(paramMap);
        Set<Entry<String, Object>> es = sort.entrySet();
        Iterator<Entry<String, Object>> it = es.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"null".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + key);
        System.out.println("HMAC source:{" + sb.toString() + "}");
        // 签名
        String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
        System.out.println("HMAC:{" + sign + "}");
        return sign;
    }

    /**
     * 验证签名, 仅支持MD5.
     *
     * @param paramMap 参数Map,不包含商户秘钥且顺序确定
     * @param key      商户秘钥
     * @param sign     签名串
     * @return 验签结果
     */
    public static boolean checkSign(Map<String, Object> paramMap, String key, String sign) {
        if (key == null) {
            throw new RuntimeException("key不能为空");
        }
        if (sign == null) {
            throw new RuntimeException("需要验签的字符为空");
        }

        return sign.equals(sign(paramMap, key));
    }


    /**
     * 通过request获取签名Map
     *
     * @param request 请求
     * @return 签名Map
     */
    public static Map<String, Object> getSignMap(HttpServletRequest request) {
        Map<String, Object> paramMap = new HashMap<>();
        Map<String, String[]> map = request.getParameterMap();
        Set<Entry<String, String[]>> es = map.entrySet();
        Iterator<Entry<String, String[]>> it = es.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            String k = (String) entry.getKey();
            Object ov = entry.getValue();
            String v = "";
            if (ov instanceof String[]) {
                String[] value = (String[]) ov;
                v = value[0];
            } else {
                v = ov.toString();
            }
            paramMap.put(k, v);
        }
        return paramMap;
    }

    /**
     * 通过url获取签名Map
     *
     * @param url 请求地址
     * @return 签名Map
     */
    @SuppressWarnings("deprecation")
    public static Map<String, Object> getSignMap(String url) {
        Map<String, Object> paramMap = new HashMap<>();
        url = url.substring(url.indexOf("?") + 1);
        String[] params = url.split("&");
        for (int i = 0; i < params.length; i++) {
            String param = params[i];
            if (param.indexOf("=") != -1) {
                String[] values = param.split("=");
                if (values != null && values.length == 2) {
                    //update----begin---author:scott----date:20160115----for:昵称转码，签名问题处理----
                    if ("nickname".equals(values[0])) {
                        paramMap.put(values[0], URLDecoder.decode(values[1]));
                    } else {
                        paramMap.put(values[0], values[1]);
                    }
                    //update----begin---author:scott----date:20160115----for:昵称转码，签名问题处理----
                }
            }
        }
        return paramMap;
    }

    /**
     * 从API返回的XML数据里面计算签名
     *
     * @param responseString API返回的XML数据
     * @param key            商户秘钥
     * @return 签名
     * @throws DocumentException 文档异常
     */
    public static String getSignFromResponseString(String responseString, String key) throws DocumentException {
        Map<String, Object> map = XmlUtils.xmlBody2map(responseString, "xml");
        // 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
        map.put("sign", "");
        // 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        return sign(map, key);
    }

    /**
     * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
     *
     * @param responseString API返回的XML数据字符串
     * @return API签名是否合法
     * @throws DocumentException 文档异常
     */
    public static boolean checkIsSignValidFromResponseString(String responseString, String key) throws DocumentException {
        Map<String, Object> map = XmlUtils.xmlBody2map(responseString, "xml");
        System.out.println(map.toString());
        Object signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == null || signFromAPIResponse == "") {
            System.out.println("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("服务器返回包里面的签名是:" + signFromAPIResponse);
        // 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
        map.put("sign", "");
        // 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
        String signForAPIResponse = sign(map, key);
        if (!signForAPIResponse.equals(signFromAPIResponse)) {
            // 签名验不过，表示这个API返回的数据有可能已经被篡改了
            System.out.println("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("恭喜，API返回的数据签名验证通过!!!");
        return true;
    }

    /**
     * 除去Map中的空值和签名参数
     *
     * @param sArray 含有签名的Map
     * @return 去掉空值与签名参数后的新签名Map
     */
    public static Map<String, String> filter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<String, String>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }

        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (StringUtils.isEmpty(value) || key.equalsIgnoreCase("sign")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的Map
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        // 第一步：把字典按Key的字母顺序排序,参数使用TreeMap已经完成排序
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        // 第二步：把所有参数名和参数值串在一起
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            String value = params.get(key);
            if (!StringUtils.isEmpty(value)) {
                sb.append(key).append("=").append(value);
            }
        }
        return sb.toString();
    }


    public static void main(String[] args) {
        System.out.println("------------------------测试从url中提取map---------------------");
        String url = "http://www.saphao.com:9999/P3-Web/commonxrs/toIndex.do?actId=402880ee51334a520151334c3eaf0001&openid=oR0jFt_DTsAUJebWqGeq3A1VWfRw&nickname=JEFF&subscribe=1&jwid=&sign=F5E56A64B650A98E67CCCFFF871C7133";
        Map<String, Object> t = getSignMap(url);
        for (Entry<String, Object> entry : t.entrySet()) {
            System.out.println(entry.getKey() + "--->" + entry.getValue());
        }
        System.out.println("------------------------测试签名过程----------------------------");
        String key = "26F72780372E84B6CFAED6F7B19139CC47B1912B6CAED753";
        Map<String, Object> paramMap = new HashMap<>();
        //paramMap.put("id", "134");
        paramMap.put("token", "edb7442d8d1a00db28062ec711f64c40");
        //paramMap.put("smsCode", "967910");
        //paramMap.put("loginName", "17792516435");
        System.out.println(createSign(paramMap, key));
    }
}
